// 18.1 异常处理
/**
 * 在5.6节（第173页）我们曾介绍过一些有关异常处理的基本概念和机理，本节将继续扩展这些知识。对于程序员来说，要想有效地使用异常处理，必须首先了解当抛出异常时发生了什么，
 *   捕获异常时发生了什么，以及用来传递错误的对象的意义。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"
#include <tuple>
#include <bitset>
#include <regex>
#include <random>
#include <cmath>
#include <iomanip>
#include <cstdio>

int main()
{
    // 18.1.1 抛出异常
    /*在C++语言中，我们通过抛出（throwing）一条表达式来引发（raised）一个异常。被抛出的表达式的类型以及当前的调用链共同决定了哪段处理代码（handler）将被用来处理该异常。
        被选中的处理代码是在调用链中与抛出对象类型匹配的最近的处理代码。
    */

    // 栈展开
    /*当抛出一个异常后，程序暂停当前函数的执行过程并立即开始寻找与异常匹配的catch子句。当throw出现在一个try语句块（try block）内时，检查与该try块关联的catch子句。
        如果找到了匹配的catch，就使用该catch处理异常。如果这一步没找到匹配的catch且该try语句嵌套在其他try块中，则继续检查与外层try匹配的catch子句。
        如果还是找不到匹配的catch，则退出当前的函数，在调用当前函数的外层函数中继续寻找。
      上述过程被称为栈展开（stack unwinding）过程。栈展开过程沿着嵌套函数的调用链不断查找，直到找到了与异常匹配的catch子句为止；或者也可能一直没找到匹配的catch，则退出主函数后查找过程终止。
      如果没找到匹配的catch子句，程序将退出。因为异常通常被认为是妨碍程序正常执行的事件，所以一旦引发了某个异常，就不能对它置之不理。
        当找不到匹配的catch时，程序将调用标准库函数terminate，顾名思义，terminate负责终止程序的执行过程。
      一个异常如果没有被捕获，则它将终止当前的程序。
    */

    // 栈展开过程中对象被自动销毁
    /*在栈展开过程中，位于调用链上的语句块可能会提前退出。通常情况下，程序在这些块中创建了一些局部对象。我们已经知道，块退出后它的局部对象也将随之销毁，这条规则对于栈展开过程同样适用。
        如果在栈展开过程中退出了某个块，编译器将负责确保在这个块中创建的对象能被正确地销毁。如果某个局部对象的类型是类类型，则该对象的析构函数将被自动调用。
        与往常一样，编译器在销毁内置类型的对象时不需要做任何事情。
    */

    // 析构函数与异常
    /*析构函数在栈展开的过程中执行，这一事实影响着我们编写析构函数的方式。在栈展开的过程中，已经引发了异常但是我们还没有处理它。如果异常抛出后没有被正确捕获，则系统将调用terminate函数。
        因此，出于栈展开可能使用析构函数的考虑，析构函数不应该抛出不能被它自身处理的异常。
        换句话说，如果析构函数需要执行某个可能抛出异常的操作，则该操作应该被放置在一个try语句块当中，并且在析构函数内部得到处理。
      在实际的编程过程中，因为析构函数仅仅是释放资源，所以它不太可能抛出异常。所有标准库类型都能确保它们的析构函数不会引发异常。
    */

    // 异常对象
    /*异常对象（exception object）是一种特殊的对象，编译器使用异常抛出表达式来对异常对象进行拷贝初始化（参见13.1.1节，第441页）。因此，throw语句中的表达式必须拥有完全类型（参见7.3.3节，第250页）。
        而且如果该表达式是类类型的话，则相应的类必须含有一个可访问的析构函数和一个可访问的拷贝或移动构造函数。如果该表达式是数组类型或函数类型，则表达式将被转换成与之对应的指针类型。
      异常对象位于由编译器管理的空间中，编译器确保无论最终调用的是哪个catch子句都能访问该空间。当异常处理完毕后，异常对象被销毁。
      如我们所知，当一个异常被抛出时，沿着调用链的块将依次退出直至找到与异常匹配的处理代码。如果退出了某个块，则同时释放块中局部对象使用的内存。
        因此，抛出一个指向局部对象的指针几乎肯定是一种错误的行为。出于同样的原因，从函数中返回指向局部对象的指针也是错误的（参见6.3.2节，第202页）。
        如果指针所指的对象位于某个块中，而该块在catch语句之前就已经退出了，则意味着在执行catch语句之前局部对象已经被销毁了。
      当我们抛出一条表达式时，该表达式的静态编译时类型（参见15.2.3节，第534页）决定了异常对象的类型。读者必须牢记这一点，因为很多情况下程序抛出的表达式类型来自于某个继承体系。
        如果一条throw表达式解引用一个基类指针，而该指针实际指向的是派生类对象，则抛出的对象将被切掉一部分（参见15.2.3节，第535页），只有基类部分被抛出。
    */

    // 18.1.2 捕获异常
    /*catch子句（catch clause）中的异常声明（exception declaration）看起来像是只包含一个形参的函数形参列表。像在形参列表中一样，如果catch无须访问抛出的表达式的话，则我们可以忽略捕获形参的名字。
      声明的类型决定了处理代码所能捕获的异常类型。这个类型必须是完全类型（参见7.3.3节，第250页），它可以是左值引用，但不能是右值引用（参见13.6.1节，第471页）。
      最后一点需要注意的是，异常声明的静态类型将决定catch语句所能执行的操作。如果catch的参数是基类类型，则catch无法使用派生类特有的任何成员。
      通常情况下，如果catch接受的异常与某个继承体系有关，则最好将该catch的参数定义成引用类型。
    */

    // 查找匹配的处理代码
    /*因为catch语句是按照其出现的顺序逐一进行匹配的，所以当程序使用具有继承关系的多个异常时必须对catch语句的顺序进行组织和管理，使得派生类异常的处理代码出现在基类异常的处理代码之前。
      如果在多个catch语句的类型之间存在着继承关系，则我们应该把继承链最底端的类（most derived type）放在前面，而将继承链最顶端的类（least derived type）放在后面。
    */

    // 重新抛出
    /*有时，一个单独的catch语句不能完整地处理某个异常。在执行了某些校正操作之后，当前的catch可能会决定由调用链更上一层的函数接着处理异常。
        一条catch语句通过重新抛出（rethrowing）的操作将异常传递给另外一个catch语句。这里的重新抛出仍然是一条throw语句，只不过不包含任何表达式：[插图] throw;
      空的throw语句只能出现在catch语句或catch语句直接或间接调用的函数之内。如果在处理代码之外的区域遇到了空throw语句，编译器将调用terminate。
      很多时候，catch语句会改变其参数的内容。如果在改变了参数的内容后catch语句重新抛出异常，则只有当catch异常声明是引用类型时我们对参数所做的改变才会被保留并继续传播：[插图]
catch(my_error &eObje) {    // 引用类型
  eObj.status = errCodes::serverErr; // 修改了异常对象
  throw;                             // 异常对象的status是serverErr
} catch(other_error eObj) { // 非引用类型
  eObj.status = errCodes::badErr;    // 只修改了异常对象的局部副本
  throw;                             // 异常对象的status成员没有变
}
    */

    // 捕获所有异常的处理代码
    /*有时我们希望不论抛出的异常是什么类型，程序都能统一捕获它们。要想捕获所有可能的异常是比较有难度的，毕竟有些情况下我们也不知道异常的类型到底是什么。
        即使我们知道所有的异常类型，也很难为所有类型提供唯一一个catch语句。为了一次性捕获所有异常，我们使用省略号作为异常声明，这称为捕获所有异常（catch-all）的处理代码，形如catch（...）。
        一条捕获所有异常的语句可以与任意类型的异常匹配。
      catch（...）通常与重新抛出语句一起使用，其中catch执行当前局部能完成的工作，随后重新抛出异常：[插图]
void manip() {
  try {
    // 这里的操作将引发并抛出一个异常
  }
  catch(...) {
    // 处理异常的某些特殊操作
    throw;
  }
}
      catch（...）既能单独出现，也能与其他几个catch语句一起出现。
      如果catch（...）与其他几个catch语句一起出现，则catch（...）必须在最后的位置。出现在捕获所有异常语句后面的catch语句将永远不会被匹配。
    */

    // 18.1.3 函数try语句块与构造函数
    /*通常情况下，程序执行的任何时刻都可能发生异常，特别是异常可能发生在处理构造函数初始值的过程中。构造函数在进入其函数体之前首先执行初始值列表。
        因为在初始值列表抛出异常时构造函数体内的try语句块还未生效，所以构造函数体内的catch语句无法处理构造函数初始值列表抛出的异常。
      要想处理构造函数初始值抛出的异常，我们必须将构造函数写成函数try语句块（也称为函数测试块，function try block）的形式。
        函数try语句块使得一组catch语句既能处理构造函数体（或析构函数体），也能处理构造函数的初始化过程（或析构函数的析构过程）。
      举个例子，我们可以把Blob的构造函数（参见16.1.2节，第586页）置于一个函数try语句块中：
template<typename T>
Blob<T>::Blob(std::initializer_list<T> il) try :
  data(std::make_shared<std::vector<T>>(il)) {
    // 空函数体
} catch(const std::bad_alloc &e) { handle_out_of_memory(e); }
      注意：关键字try出现在表示构造函数初始值列表的冒号以及表示构造函数体（此例为空）的花括号之前。与这个try关联的catch既能处理构造函数体抛出的异常，也能处理成员初始化列表抛出的异常。
      函数try语句块只能处理构造函数开始执行后发生的异常。和其他函数调用一样，如果在参数初始化的过程中发生了异常，则该异常属于调用表达式的一部分，并将在调用者所在的上下文中处理。
      处理构造函数初始值异常的唯一方法是将构造函数写成函数try语句块。
    */

    // 18.1.4 noexcept异常说明
    /*对于用户及编译器来说，预先知道某个函数不会抛出异常显然大有裨益。首先，知道函数不会抛出异常有助于简化调用该函数的代码；
        其次，如果编译器确认函数不会抛出异常，它就能执行某些特殊的优化操作，而这些优化操作并不适用于可能出错的代码。
      在C++11新标准中，我们可以通过提供noexcept说明（noexcept specification）指定某个函数不会抛出异常。其形式是关键字noexcept紧跟在函数的参数列表后面，用以标识该函数不会抛出异常：[插图]
void recoup(int) noexcept; // 不会抛出异常
void alloc(int);           // 可能抛出异常
      对于一个函数来说，noexcept说明要么出现在该函数的所有声明语句和定义语句中，要么一次也不出现。该说明应该在函数的尾置返回类型（参见6.3.3节，第206页）之前。
        我们也可以在函数指针的声明和定义中指定noexcept。在typedef或类型别名中则不能出现noexcept。在成员函数中，noexcept说明符需要跟在const及引用限定符之后，而在final、override或虚函数的=0之前。
    */

    // 违反异常说明
    /*读者需要清楚的一个事实是编译器并不会在编译时检查noexcept说明。实际上，如果一个函数在说明了noexcept的同时又含有throw语句或者调用了可能抛出异常的其他函数，编译器将顺利编译通过，
        并不会因为这种违反异常说明的情况而报错（不排除个别编译器会对这种用法提出警告）：
void f() noexcept    // 承诺不会抛出异常
{
  throw exception(); // 违反了异常说明
}
      noexcept可以用在两种情况下：一是我们确认函数不会抛出异常，二是我们根本不知道该如何处理异常。
    */

    // 向后兼容：异常说明
    /*早期的C++版本设计了一套更加详细的异常说明方案，该方案使得我们可以指定某个函数可能抛出的异常类型。函数可以指定一个关键字throw，在后面跟上括号括起来的异常类型列表。
        throw说明符所在的位置与新版本C++中noexcept所在的位置相同。上述使用throw的异常说明方案在C++11新版本中已经被取消了。然而尽管如此，它还有一个重要的用处。
        如果函数被设计为是throw（）的，则意味着该函数将不会抛出异常：[插图]
void recoup(int) noexcept; // 承诺不会抛出异常
void recoup(int) throw();  // 等价的声明
        上面的两条声明语句是等价的，它们都承诺recoup不会抛出异常。
    */

    // 异常说明的实参
    /*noexcept说明符接受一个可选的实参，该实参必须能转换为bool类型：如果实参是true，则函数不会抛出异常；如果实参是false，则函数可能抛出异常：[插图]
void recoup(int) noexcept(true); // 承诺不会抛出异常
void alloc(int) noexcept(false); // 可能抛出异常
    */

    // noexcept运算符
    /*noexcept说明符的实参常常与noexcept运算符（noexcept operator）混合使用。noexcept运算符是一个一元运算符，它的返回值是一个bool类型的右值常量表达式，用于表示给定的表达式是否会抛出异常。
      例如，因为我们声明recoup时使用了noexcept说明符，所以下面的表达式的返回值为true：[插图]
noexcept(recoup(i)); // 如果recoup不抛出异常则结果为true；否则为false
      我们可以使用noexcept运算符得到如下的异常说明：[插图]
void f() noexcept(noexcept(g())); // f和g的异常说明一致
      如果函数g承诺了不会抛出异常，则f也不会抛出异常；如果g没有异常说明符，或者g虽然有异常说明符但是允许抛出异常，则f也可能抛出异常。
      noexcept有两层含义：当跟在函数参数列表后面时它是异常说明符；而当作为noexcept异常说明的bool实参出现时，它是一个运算符。
    */

    // 异常说明与指针、虚函数和拷贝控制
    /*函数指针及该指针所指的函数必须具有一致的异常说明。也就是说，如果我们为某个指针做了不抛出异常的声明，则该指针将只能指向不抛出异常的函数。
        相反，如果我们显式或隐式地说明了指针可能抛出异常，则该指针可以指向任何函数，即使是承诺了不抛出异常的函数也可以：[插图]
void (*pf1)(int) noexcept = recoup; // recoup和pf1都承诺不会抛出异常
void (*pf2)(int) = recoup;          // 正确：recoup不会抛出异常，pf2可能抛出异常，二者之间互不干扰
pf1 = alloc; // 错误：alloc可能抛出异常，但是pf1已经说明了它不会抛出异常
pf2 = alloc; // 正确：alloc和pf2都可能抛出异常
      如果一个虚函数承诺了它不会抛出异常，则后续派生出来的虚函数也必须做出同样的承诺；与之相反，如果基类的虚函数允许抛出异常，则派生类的对应函数既可以允许抛出异常，也可以不允许抛出异常：[插图]
class Base {
public:
  virtual double f1(double) noexcept; // 承诺不会抛出异常
  virtual int f2() noexcept(false);   // 可能抛出异常
  virtual void f3();                  // 可能抛出异常
};
class Derived : public Base {
public:
  double f1(double);        // 错误：Base::f1承诺不会抛出异常
  int f2() noexcept(false); // 正确：与Base::f2的异常说明一致
  void f3() noexcept;       // 正确：Derived的f3做了更严格的限定，这是允许的
};
      当编译器合成拷贝控制成员时，同时也生成一个异常说明。如果对所有成员和基类的所有操作都承诺了不会抛出异常，则合成的成员是noexcept的。
        如果合成成员调用的任意一个函数可能抛出异常，则合成的成员是noexcept（false）。而且，如果我们定义了一个析构函数但是没有为它提供异常说明，则编译器将合成一个。
        合成的异常说明将与假设由编译器为类合成析构函数时所得的异常说明一致。
    */

    // 18.1.5 异常类层次
    /*标准库异常类（参见5.6.3节，第176页）构成了图18.1所示的继承体系（参见第15章）。[插图]
图18.1：标准exception类层次
exception
  bad_cast
  runtime_error
    overflow_error
    underflow_error
    range_error
  logic_error
    domain_error
    invalid_argument
    out_of_range
    length_error
  bad_alloc
      类型exception仅仅定义了拷贝构造函数、拷贝赋值运算符、一个虚析构函数和一个名为what的虚成员。其中what函数返回一个const char＊，该指针指向一个以unll结尾的字符数组，并且确保不会抛出任何异常。
      类exception、bad_cast和bad_alloc定义了默认构造函数。类runtime_error和logic_error没有默认构造函数，但是有一个可以接受C风格字符串或者标准库string类型实参的构造函数，
        这些实参负责提供关于错误的更多信息。在这些类中，what负责返回用于初始化异常对象的信息。因为what是虚函数，所以当我们捕获基类的引用时，对what函数的调用将执行与异常对象动态类型对应的版本。
    */

    // 书店应用程序的异常类
    /*实际的应用程序通常会自定义exception（或者exception的标准库派生类）的派生类以扩展其继承体系。这些面向应用的异常类表示了与应用相关的异常条件。
      复杂性的一个方面就是如何处理异常。实际上，我们很可能需要建立一个自己的异常类体系，用它来表示与应用相关的各种问题。我们设计的异常类可能如下所示：
// 某个书店应用程序设定的异常类
class out_of_stock: public std::runtime_error {
public:
  explicit out_of_stock(const std::string &s): std::runtime_error(s) {}
};
class isbn_mismatch: public std::logic_error {
public:
  explicit isbn_mismatch(const std::string &s): std::logic_error(s) {}
  isbn_mismatch(cosnt std::string &s, const std::string &lhs, const std::string &rhs) :
                std::logic_error(s), left(lhs), right(rhs) {}
  const std::string left, right;
};
      由上可知，我们的面向应用的异常类继承自标准异常类。和其他继承体系一样，异常类也可以看作按照层次关系组织的。层次越低，表示的异常情况就越特殊。
        例如，在异常类继承体系中位于最顶层的通常是exception，exception表示的含义是某处出错了，至于错误的细节则未作描述。
    */

    // 使用我们自己的异常类型
    /*我们使用自定义异常类的方式与使用标准异常类的方式完全一样。程序在某处抛出异常类型的对象，在另外的地方捕获并处理这些出现的问题。
      举个例子，我们可以为Sales_data类定义一个复合加法运算符，当检测到参与加法的两个ISBN编号不一致时抛出名为isbn_mismatch的异常：
Sales_data&
Sales_data::operator+=(const Sales_data &rhs) {
  if(isbn() != rhs.isbn())
    throw isbn_mismatch("ISBN mismatch", isbn(), rhs.isbn());
  units_sold += rhs.units_sold;
  revenue += rhs.revenue;
  return *this;
}
      使用了复合加法运算符的代码将能检测到这一错误，进而输出一条相应的错误信息并继续完成其他任务：
// 使用之前设定的书店程序异常类
Sales_data item1, item2, sum;
while(cin >> item1 >> item2) { // 读取两条交易信息
  try {
    sum = item1 + item2;       // 计算它们的和
    // 此处使用sum
  }
  catch(const isbn_mismatch &e) {
    cerr << e.what() << ": left isbn(" << e.left << ") right isbn(" << e.right << ")" << endl;
  }
}
    */

    return 0;
}